// +build !debug
package main

import (
	"errors"
	"fmt"
	_ "github.com/ihacklog/easyAria/statik"
	"github.com/rakyll/statik/fs"
	"github.com/zserge/webview"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"
)

type Aria2Arch struct {
	Path, InstallCmd string
}

var Aria2Bin = make(map[string]Aria2Arch)
var Aria2Path Aria2Arch

const (
	windowWidth    = 1024
	windowHeight   = 768
	ariaListenPort = "6801"
	ariaUserAgent  = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"
)

func init() {
	Aria2Bin["darwin"] = Aria2Arch{Path: "/usr/local/bin/aria2c", InstallCmd: "brew install aria2"}
	Aria2Bin["linux"] = Aria2Arch{Path: "/usr/bin/aria2c", InstallCmd: "sudo pacman -S aria2c"}
	//@TODO windows add auto upgrade feature
	//https://github.com/aria2/aria2/releases/latest
	//Location: https://github.com/aria2/aria2/releases/tag/release-1.34.0
	//https://github.com/aria2/aria2/releases/download/release-1.34.0/aria2-1.34.0-win-64bit-build1.zip
	ex, err := os.Executable()
	if err != nil {
		panic(err)
	}
	exPath := filepath.Dir(ex)
	Aria2Bin["windows"] = Aria2Arch{Path: exPath + string(filepath.Separator) + "aria2c.exe", InstallCmd: "download on https://aria2.github.io/"}
	Aria2Path = Aria2Bin[runtime.GOOS]
}

var isAriaPrevStarted bool = false
var isAriaNotExists bool = false

func copyAndCapture(w io.Writer, r io.Reader) ([]byte, error) {
	var out []byte
	buf := make([]byte, 1024, 1024)
	for {
		n, err := r.Read(buf[:])
		if n > 0 {
			d := buf[:n]
			out = append(out, d...)
			_, err := w.Write(d)
			if err != nil {
				return out, err
			}
		}
		if err != nil {
			// Read returns io.EOF at the end of file, which is not an error for us
			if err == io.EOF {
				err = nil
			}
			return out, err
		}
	}
	// never reached
	panic(true)
	return nil, nil
}

func UserHomeDir() string {
	if runtime.GOOS == "windows" {
		home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
		if home == "" {
			home = os.Getenv("USERPROFILE")
		}
		return home
	}
	return os.Getenv("HOME")
}

func isAria2Started() bool {
	timeout := time.Millisecond * 100
	conn, _ := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", ariaListenPort), timeout)
	if conn != nil {
		conn.Close()
		return true
	}
	return false
}

func startAria2Rpc(done <-chan struct{}) error {

	if isAria2Started() {
		isAriaPrevStarted = true

		go func() {
			for {
				select {
				case <-done:
					log.Printf("done chan recv.\n")
				default:
				}
			}
		}()

		return nil
	}

	//check bin exists mac: "/usr/local/bin/aria2c"
	aria2cPath := Aria2Path.Path
	if _, err := os.Stat(aria2cPath); os.IsNotExist(err) {
		isAriaNotExists = true
		return errors.New(aria2cPath + " does not exists!\n please install it with :\n " + Aria2Path.InstallCmd)
	}

	downloadDir := UserHomeDir() + string(filepath.Separator) + "Downloads"
	confDir := UserHomeDir() + string(filepath.Separator) + ".easyAria"
	sessionFilepath := confDir + string(filepath.Separator) + "aria2.sess"

	if _, err := os.Stat(downloadDir); os.IsNotExist(err) {
		os.Mkdir(downloadDir, os.ModePerm)
	}
	if _, err := os.Stat(confDir); os.IsNotExist(err) {
		os.Mkdir(confDir, os.ModePerm)
	}
	//https://stackoverflow.com/questions/35558787/create-an-empty-text-file/35558965
	f, _ := os.OpenFile(sessionFilepath, os.O_RDONLY|os.O_CREATE, 0666)
	f.Close()

	aria2Params := []string{
		fmt.Sprintf("--user-agent='%s'", ariaUserAgent),
		"--allow-overwrite=false",
		"--force-save=false",
		"-c",
		"--file-allocation=none",
		"--log-level=error",
		"--max-tries=0",   //unlimited number of tries
		"--retry-wait=30", // seconds to wait between retries
		"--split=64", //Download a file using N connections
		"-x16",            //--max-connection-per-server
		"--max-file-not-found=0", //default 0
		"-k1M", //--min-split-size
		"--no-conf",
		"-Rfalse", //--remote-time
		"--summary-interval=0",
		"--timeout=600", //timeout must be between 1 and 600
		"--check-certificate=false",
		"--enable-rpc=true",
		"--rpc-allow-origin-all=true",
		"--rpc-listen-port=" + ariaListenPort,
		//"--load-cookies=/home/hacklog/Videos/cookies.txt",
		"-d " + downloadDir,
		"--input-file=" + sessionFilepath,
		"--save-session=" + sessionFilepath,
		"--save-session-interval=30",
		fmt.Sprintf("--stop-with-process=%d", os.Getpid()),
		"--rpc-secret=123456",
	}

	/*	ctx, cancel := context.WithCancel(context.Background())
		cmd := exec.CommandContext(ctx, aria2cPath, aria2Params...)
	*/
	cmd := exec.Command(aria2cPath, aria2Params...)
	prepareBackgroundCommand(cmd)

	var stdout, stderr []byte
	var errStdout, errStderr error
	stdoutIn, _ := cmd.StdoutPipe()
	stderrIn, _ := cmd.StderrPipe()
	cmd.Start()

	go func() {
		stdout, errStdout = copyAndCapture(os.Stdout, stdoutIn)
	}()

	go func() {
		stderr, errStderr = copyAndCapture(os.Stderr, stderrIn)
	}()

	if errStdout != nil || errStderr != nil {
		log.Fatalf("failed to capture stdout or stderr\n")
	}
	outStr, errStr := string(stdout), string(stderr)
	if len(outStr) > 0 || len(outStr) > 0 {
		fmt.Printf("\nout:\n%s\nerr:\n%s\n", outStr, errStr)
	}

	// Abort the walk if done is closed.
	go func() {
		for {
			select {
			case <-done:
				cmd.Process.Signal(os.Kill)
				log.Printf("aria2c process killed")
			default:
			}
		}
	}()

	err := cmd.Wait()
	if err != nil {
		//log.Fatalf("cmd.Run() failed with %s\n", err)
	}
	return nil
}

func startServer() string {
	ln, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		log.Fatal(err)
	}
	go func() {
		defer ln.Close()

		statikFS, err := fs.New()
		if err != nil {
			log.Fatal(err)
		}

		log.Fatal(http.Serve(ln, http.FileServer(statikFS)))
	}()
	log.Printf("listen addr: %s", "http://"+ln.Addr().String())
	return "http://" + ln.Addr().String()
}

func handleRPC(w webview.WebView, data string) {
	switch {
	case data == "close":
		w.Terminate()
	case data == "fullscreen":
		w.SetFullscreen(true)
	case data == "unfullscreen":
		w.SetFullscreen(false)
	case data == "open":
		log.Println("open", w.Dialog(webview.DialogTypeOpen, 0, "Open file", ""))
	case data == "opendir":
		log.Println("open", w.Dialog(webview.DialogTypeOpen, webview.DialogFlagDirectory, "Open directory", ""))
	case data == "save":
		log.Println("save", w.Dialog(webview.DialogTypeSave, 0, "Save file", ""))
	case data == "message":
		w.Dialog(webview.DialogTypeAlert, 0, "Hello", "Hello, world!")
	case data == "info":
		w.Dialog(webview.DialogTypeAlert, webview.DialogFlagInfo, "Hello", "Hello, info!")
	case data == "warning":
		w.Dialog(webview.DialogTypeAlert, webview.DialogFlagWarning, "Hello", "Hello, warning!")
	case data == "error":
		w.Dialog(webview.DialogTypeAlert, webview.DialogFlagError, "Hello", "Hello, error!")
	case strings.HasPrefix(data, "changeTitle:"):
		w.SetTitle(strings.TrimPrefix(data, "changeTitle:"))
	}
}

func main() {
	done := make(chan struct{})
	go startAria2Rpc(done)

	url := startServer()
	w := webview.New(webview.Settings{
		Width:     windowWidth,
		Height:    windowHeight,
		Title:     "Easy Aria",
		Resizable: true,
		URL:       url,
		ExternalInvokeCallback: handleRPC,
		Debug: false,
	})

	w.Dispatch(func() {
		w.SetColor(255, 255, 255, 255)
		log.Printf("url: %s\n", url)
		// localStorage.getItem('AriaNg.Options')
		//w.Eval("setTimeout(function() {window.location.reload();}, 800);")
		if isAriaPrevStarted {
			w.Dialog(webview.DialogTypeAlert, 0, "Warning",
				"aria2c has been started by previous EasyAria or other program.")
		}
		if isAriaNotExists {
			w.Dialog(webview.DialogTypeAlert, 0, "Warning",
				"aria2 does not exists!\n please install it with command:\n brew install aria2")
		}
	})

	defer func() {
		log.Printf("start send msg to done")
		done <- struct{}{}
		w.Exit()
	}()
	w.Run()
}
